package parser

import (
	"fmt"
	"github.com/fatih/color"
	"github.com/hashicorp/hcl/v2"
	"github.com/ihaiker/tfwrapper/internal/config"
	"github.com/ihaiker/tfwrapper/internal/errors"
	"github.com/ihaiker/tfwrapper/internal/logger"
	"github.com/ihaiker/tfwrapper/internal/util"
	"path"
	"path/filepath"
)

const stackGlobalConfigName = "stack.hcl"

type stackHclFile struct {
	stack    string
	source   string
	filename string
	hclFile  *hcl.File
}

// isStackGlobalConfig 是否是stack域的配置内容
func (stackHclFile *stackHclFile) isStackGlobalConfig() bool {
	return path.Base(stackHclFile.filename) == stackGlobalConfigName
}

type stackHclFiles []stackHclFile

func (s stackHclFiles) HclFiles() []*hcl.File {
	files := make([]*hcl.File, len(s))
	for i, file := range s {
		files[i] = file.hclFile
	}
	return files
}

// StackConfigurationFile 查找 stack 的全局配置文件，此配置文件必须为stack.hcl文件
func (s stackHclFiles) StackConfigurationFile() *stackHclFile {
	for _, file := range s {
		if file.isStackGlobalConfig() {
			return &file
		}
	}
	return nil
}

// walkStackHclFile 查找 stack source 定义的文件夹中配置文件，并解析为 hcl.File
func walkStackHclFile(stack config.Stack, cfg config.TFWrapperConfig) (files stackHclFiles, diags hcl.Diagnostics) {
	files = make([]stackHclFile, 0)

	stackRoot := path.Dir(cfg.CurrentConfigPath)

	var hclFile *hcl.File
	for _, source := range stack.Source {
		filenames, err := util.Walk(path.Join(stackRoot, source), util.IsHcl)
		if err != nil {
			return nil, errors.Diagnostics("search stack source",
				fmt.Errorf("walk stack source %s error: %s", source, errors.WithStackTrace(err)))
		}
		if len(filenames) == 0 {
			return nil, errors.Diagnostics("not found",
				fmt.Errorf("stack source %s not found any files", color.HiRedString(source)))
		}

		for _, filename := range filenames {
			if hclFile, diags = util.ParseHcl(filename); diags.HasErrors() {
				return
			}
			files = append(files, stackHclFile{
				stack:    stack.Name,
				source:   source,
				filename: filename,
				hclFile:  hclFile,
			})
		}
	}
	return
}

// parseStackGlobalConfig 解析stack级别通用配置，在整个stack中都会生效
func parseStackGlobalConfig(cfg config.TFWrapperConfig, stack config.Stack, flags config.Flags, stackFiles stackHclFiles) (stackConfig *config.StackConfig, diags hcl.Diagnostics) {
	stackGlobalsFile := stackFiles.StackConfigurationFile()
	if stackGlobalsFile == nil {
		return stackConfig, nil
	}

	stackConfig = &config.StackConfig{Stack: stack}
	stackConfig.WrapperConfigPath = cfg.CurrentConfigPath
	stackConfig.CurrentConfigPath = stackGlobalsFile.filename
	stackConfig.Running.Stack = stack.Name
	stackConfig.Running.StackSourcesDir = stack.Source
	stackConfig.Running.StackSourceDir = stackGlobalsFile.source

	running := config.Clone(cfg.Running, stackConfig.Running)
	running.StackSourceDir = stackGlobalsFile.source
	running.CurrentConfigPath = stackGlobalsFile.filename

	if diags = evaluateConstraint(&stackConfig.Running, &cfg.Running, flags, stackGlobalsFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackConfig.Functions, diags = parseFunctions(running, stackGlobalsFile.hclFile); util.HasErrors(diags) {
		return
	}
	running.Functions = append(running.Functions, stackConfig.Functions...)

	if stackConfig.Globals, diags = evaluateGlobalsBlock(running, stackGlobalsFile.hclFile); util.HasErrors(diags) {
		return
	}
	running.Globals = util.Merge(running.Globals, stackConfig.Globals)

	if stackConfig.Locals, diags = evaluateLocalsBlock(running, stackGlobalsFile.hclFile); util.HasErrors(diags) {
		return
	}
	running.Locals = util.Merge(running.Locals, stackConfig.Locals)

	if stackConfig.Inputs, diags = evaluateInputsBlock(running, stackGlobalsFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackConfig.Generators, diags = parseGenerator(running, stackGlobalsFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackConfig.Terraform, diags = parseTerraform(running, stackGlobalsFile.hclFile); util.HasErrors(diags) {
		return
	}
	return
}

// parseStackFileConfig 解析stack普通配置文件
func parseStackFileConfig(stack config.Stack, cfg config.TFWrapperConfig, stackConfig config.StackConfig, flags config.Flags, stackHclFile stackHclFile) (stackFileConfig *config.StackFileConfig, diags hcl.Diagnostics) {

	running := config.Clone(cfg.Running, stackConfig.Running)
	running.Stack = stack.Name
	running.StackSourcesDir = stack.Source
	running.StackSourceDir = stackHclFile.source
	running.CurrentConfigPath = stackHclFile.filename

	stackFileConfig = new(config.StackFileConfig)

	stackFileConfig.WrapperConfigPath = cfg.CurrentConfigPath
	stackFileConfig.CurrentConfigPath = stackHclFile.filename
	stackFileConfig.Running.Stack = stack.Name
	stackFileConfig.Running.StackSourcesDir = stack.Source
	stackFileConfig.Running.StackSourceDir = stackHclFile.source

	if diags = evaluateConstraint(&stackFileConfig.Running, &stackConfig.Running, flags, stackHclFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackFileConfig.Functions, diags = parseFunctions(running, stackHclFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackFileConfig.Locals, diags = evaluateLocalsBlock(running, stackHclFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackFileConfig.Inputs, diags = evaluateInputsBlock(running, stackHclFile.hclFile); util.HasErrors(diags) {
		return
	}
	if stackFileConfig.Generators, diags = parseGenerator(running, stackHclFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackFileConfig.Terraform, diags = parseTerraform(running, stackHclFile.hclFile); util.HasErrors(diags) {
		return
	}

	if stackFileConfig.Modules, diags = parseModule(running, stackHclFile.hclFile); util.HasErrors(diags) {
		return
	}

	return
}

// parseStackSourceConfig 解析stack source目录下对应的文件
func parseStackSourceConfig(stack config.Stack, cfg config.TFWrapperConfig, flags config.Flags) (*config.StackConfig, hcl.Diagnostics) {
	log := logger.Logger(cfg.Running)

	stackFiles, diags := walkStackHclFile(stack, cfg)
	if util.HasErrors(diags) {
		return nil, diags
	}
	for _, file := range stackFiles {
		rel, _ := filepath.Rel(path.Dir(cfg.WrapperConfigPath), file.filename)
		log.Infof("found stack config file: %s", rel)
	}

	stackConfig, diags := parseStackGlobalConfig(cfg, stack, flags, stackFiles)
	if util.HasErrors(diags) {
		return nil, diags
	}

	for _, stackFile := range stackFiles {
		//skip stack global config, is parse in first
		if stackFile.isStackGlobalConfig() {
			continue
		}
		stackFileConfig, diags := parseStackFileConfig(stack, cfg, *stackConfig, flags, stackFile)
		if util.HasErrors(diags) {
			return nil, diags
		}
		stackConfig.Files = append(stackConfig.Files, stackFileConfig)
	}

	if diags = checkForDuplicateDefinitions(*stackConfig); util.HasErrors(diags) {
		return nil, diags
	}

	return stackConfig, diags
}
